Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

비동기 처리 시 메서드를 직접 호출하는 코드를 이벤트 발행으로 변경 #871

Merged
merged 4 commits into from
Jan 20, 2025

Conversation

Hanjaemo
Copy link
Member

@Hanjaemo Hanjaemo commented Jan 19, 2025

💡 작업 내용

  • 톡픽 관련 비동기 처리 시 직접 호출하는 로직을 이벤트 발행으로 변경
  • 요약에 실패한 톡픽에 대한 재요약 스케줄링 코드 개선

💡 자세한 설명

#870 해당 이슈의 원인을 다음과 같이 분석했습니다.

  • @Async가 붙은 summarizeTalkPick 메서드는 이를 호출하는 메인 스레드의 작업, 즉 톡픽 생성 메서드와 다른 트랜잭션 주기를 가짐
  • 따라서 메인 스레드의 트랜잭션이 커밋되기 전에 비동기 작업에서 데이터베이스를 조회하면 데이터가 없을 수 있음
  • 예시:
    1. create tx 톡픽 저장
    2. summarize tx 톡픽 조회 → 에러 발생
    3. create tx 커밋

이러한 이유로, 비동기 작업의 실행 시점을 트랜잭션 커밋 이후로 보장하기 위해 @TransactionalEventListener를 사용한 이벤트 기반으로 리팩토링했습니다.

직접 호출 방식에서 이벤트 발행으로 변경

메인 메서드에서 비동기 작업을 직접 호출하는 대신, 이벤트를 발행하는 방식으로 변경했습니다.

@Transactional
public Long createTalkPick(CreateTalkPickRequest request, ApiMember apiMember) {

    ...

    TalkPick savedTalkPick = talkPickRepository.save(request.toEntity(member));
    Long savedTalkPickId = savedTalkPick.getId();

    eventPublisher.publishEvent(new TalkPickCreatedEvent(savedTalkPickId, request.getFileIds()));

    ...
}

이를 통해 트랜잭션 커밋 전에 비동기 작업이 실행되어 발생하는 오류를 해결했습니다.

요약에 실패한 톡픽에 대한 재요약 스케줄링 코드 개선

기존 코드에서 스케줄링 메서드 실행 시 비동기 스레드를 사용했습니다.
하지만 스케줄링 작업은 단일 스레드에서 실행해도 충분하며, 별도의 비동기 스레드를 생성하여 실행하면 시스템 리소스를 불필요하게 소비하게 될 것이라 판단했습니다.
따라서 비동기 메서드를 실행하는게 아니라, 단일 스레드에서 실행되도록 처리했습니다.

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요?
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 작업 도중 문서 수정이 필요한 경우 잘 수정했나요?
  • 테스트는 잘 통과했나요?
  • 불필요한 코드는 제거했나요?

closes #870

Summary by CodeRabbit

  • 새 기능

    • 토크픽 생성, 업데이트, 삭제 시 이벤트 기반 처리 도입
    • 토크픽 요약 서비스 추가
    • 요약 상태에 따른 토크픽 필터링 기능 추가
  • 버그 수정

    • 요약 상태에 따른 토크픽 필터링 메커니즘 최적화
  • 리팩토링

    • 파일 및 요약 처리를 위한 이벤트 핸들러 도입
    • 서비스 계층 구조 및 책임 재정립
    • 불필요한 메서드 제거 및 코드 구조 개선

@Hanjaemo Hanjaemo added 🪄 refactor 리팩토링 🛠️ fix 오류 수정 D-0 긴급 이슈에 대한 작업 labels Jan 19, 2025
@Hanjaemo Hanjaemo self-assigned this Jan 19, 2025
Copy link
Contributor

coderabbitai bot commented Jan 19, 2025

Walkthrough

이 풀 리퀘스트는 톡픽(TalkPick) 관련 서비스의 아키텍처를 이벤트 기반으로 리팩토링하는 작업을 포함합니다. 주요 변경사항은 파일 처리, 요약 서비스, 이벤트 핸들링 로직의 재구조화입니다. 기존의 직접적인 의존성을 제거하고 ApplicationEventPublisher를 통한 느슨한 결합 방식으로 전환하여 시스템의 유연성을 높였습니다.

Changes

파일 변경 요약
TalkPickFileService.java 클래스 이름 및 패키지 변경, 파일 처리 로직 간소화, 메서드 제거
TalkPickScheduleService.java 요약 실패 처리 로직 TalkPickSummaryService로 위임, 필드 이름 변경
TalkPickService.java 이벤트 기반 아키텍처로 전환, 파일 처리 로직 제거, 이벤트 클래스 추가
TalkPickSummaryService.java 새로운 요약 서비스 클래스 추가, 요약 메서드 구현
*Event.java 생성, 업데이트, 삭제 이벤트 클래스 추가
TalkPickRepository.java 요약 상태에 따라 TalkPick 조회 메서드 추가
TalkPickRepositoryCustom.java 요약 상태 기반 ID 조회 메서드 제거
TalkPickRepositoryImpl.java 요약 상태 기반 ID 조회 메서드 제거
TalkPickServiceTest.java 테스트에서 TalkPickFileHandlerFileRepository로 변경

Assessment against linked issues

목표 해결 여부 설명
NOT_FOUND_TALK_PICK 예외 처리 [#870]
비동기 요약 작업 안정성 개선 [#870]

Possibly related PRs

Suggested labels

✨ feature, D-1

Suggested reviewers

  • gywns0417
  • jschoi-96

Poem

🐰 코드의 토끼가 뛰어다녀요
이벤트로 춤추는 서비스들
리팩토링의 마법을 부리며
더 나은 길을 열어가네! 🌈

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)

비동기 메서드에서 매개변수 리스트의 안전한 복사본 생성 필요

handleFilesOnTalkPickUpdate 메서드가 @Async로 비동기 실행되므로, 매개변수로 전달된 리스트의 직접적인 수정은 동시성 문제를 일으킬 수 있습니다. 다음과 같이 수정하는 것이 안전합니다:

public void handleFilesOnTalkPickUpdate(List<Long> newFileIds, List<Long> deleteFileIds, Long talkPickId) {
    deleteFiles(deleteFileIds);
    List<Long> safeNewFileIds = new ArrayList<>(newFileIds);
    safeNewFileIds.removeIf(deleteFileIds::contains);
    relocateFiles(safeNewFileIds, talkPickId);
}
🔗 Analysis chain

Line range hint 41-45: 업데이트 메서드의 동시성 이슈 가능성 검토 필요

newFileIds.removeIf((deleteFileIds::contains)) 연산이 동시성 문제를 일으킬 수 있습니다. 리스트 수정 전에 복사본을 만드는 것이 안전할 수 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 동시성 관련 이슈가 있는지 코드베이스 검사
rg -l "handleFilesOnTalkPickUpdate.*@Async" | xargs rg "removeIf|remove|clear|add" -A 5 -B 5

Length of output: 44611

🧹 Nitpick comments (7)
src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java (1)

14-14: 비동기 처리를 위한 메서드 시그니처가 적절합니다.

findAllBySummaryStatus 메서드는:

  • Spring Data JPA의 명명 규칙을 잘 따르고 있습니다
  • 전체 엔티티를 반환하여 N+1 문제를 방지할 수 있습니다
  • 이벤트 기반 처리를 위한 데이터 조회에 적합합니다

하나 제안드리고 싶은 점은, 대량의 데이터 처리가 예상된다면 페이징 처리를 고려해보시는 것도 좋을 것 같습니다.

src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java (1)

16-16: 실패한 요약 재시도 로직이 효과적으로 개선되었습니다.

기존의 복잡한 구현이 단일 서비스 메서드 호출로 단순화되어 다음과 같은 이점이 있습니다:

  • 코드의 가독성 향상
  • 관심사의 명확한 분리
  • 불필요한 비동기 처리 제거로 리소스 사용 최적화
src/main/java/balancetalk/talkpick/application/TalkPickService.java (1)

57-58: 파일 조회 시 데이터베이스 호출 최적화 제안

fileRepository를 사용하여 이미지 URL과 파일 ID를 각각 조회하고 있는데, 이는 두 번의 데이터베이스 호출을 발생시킬 수 있습니다. 한 번의 쿼리로 필요한 정보를 모두 가져올 수 있도록 리포지토리 메서드를 개선하는 것을 고려해보시기 바랍니다.

src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java (1)

22-27: 업데이트 이벤트에서 불필요한 요약 작업 검토 필요

파일만 변경되고 내용이 변경되지 않은 경우에도 요약 작업이 수행됩니다. 내용이 변경된 경우에만 요약 작업을 수행하도록 최적화가 필요합니다.

src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)

Line range hint 55-65: 존재 여부 확인 로직 최적화 필요

notExistsFilesBy 메서드를 호출한 후 다시 파일을 조회하는 것은 비효율적일 수 있습니다. 한 번의 쿼리로 처리하는 것이 좋겠습니다.

    @Async
    @Retryable(backoff = @Backoff(delay = 1000))
    @Transactional
    public void handleFilesOnTalkPickDelete(Long talkPickId) {
-       if (notExistsFilesBy(talkPickId)) {
-           return;
-       }
        List<File> files = fileRepository.findAllByResourceIdAndFileType(talkPickId, TALK_PICK);
+       if (files.isEmpty()) {
+           return;
+       }
        fileHandler.deleteFiles(files);
    }
-
-    private boolean notExistsFilesBy(Long talkPickId) {
-        return !fileRepository.existsByResourceIdAndFileType(talkPickId, TALK_PICK);
-    }
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (2)

71-73: 테스트 데이터 설정이 개선되어야 합니다.

빈 리스트 대신 실제와 유사한 테스트 데이터를 사용하여 테스트하는 것이 좋겠습니다.

-       when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of());
-       when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of());
+       when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK))
+           .thenReturn(List.of("image1.jpg", "image2.jpg"));
+       when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK))
+           .thenReturn(List.of(1L, 2L));

Line range hint 87-105: 테스트 케이스 중복 코드 제거 필요

여러 테스트에서 반복되는 given 부분을 @beforeeach로 이동하여 코드 중복을 제거하는 것이 좋겠습니다.

    @BeforeEach
    void setUp() {
        // ... 기존 코드 ...
+       when(talkPickRepository.findById(1L)).thenReturn(Optional.ofNullable(talkPick));
+       when(guestOrApiMember.isGuest()).thenReturn(true);
+       when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK))
+           .thenReturn(List.of("image1.jpg"));
+       when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK))
+           .thenReturn(List.of(1L));
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4d3f4 and d650287.

📒 Files selected for processing (12)
  • src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (3 hunks)
  • src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java (1 hunks)
  • src/main/java/balancetalk/talkpick/application/TalkPickService.java (4 hunks)
  • src/main/java/balancetalk/talkpick/application/TalkPickSummaryService.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/event/TalkPickCreatedEvent.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/event/TalkPickDeletedEvent.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/event/TalkPickUpdatedEvent.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java (1 hunks)
  • src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryCustom.java (0 hunks)
  • src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryImpl.java (0 hunks)
  • src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (5 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryImpl.java
  • src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepositoryCustom.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/balancetalk/talkpick/domain/event/TalkPickDeletedEvent.java
🔇 Additional comments (12)
src/main/java/balancetalk/talkpick/domain/repository/TalkPickRepository.java (1)

3-14: 리포지토리 인터페이스가 잘 개선되었습니다!

기존의 커스텀 구현에서 Spring Data JPA의 규약 기반 쿼리 메서드로 변경한 것이 좋은 선택입니다. 이는 코드를 더 간단하고 유지보수하기 쉽게 만들었습니다.

src/main/java/balancetalk/talkpick/application/TalkPickScheduleService.java (1)

11-11: 의존성 주입 필드명이 역할을 더 잘 표현하도록 개선되었습니다.

TalkPickSummarizer에서 TalkPickSummaryService로의 변경은 해당 컴포넌트의 서비스 계층으로서의 역할을 더 명확하게 표현합니다.

src/main/java/balancetalk/talkpick/application/TalkPickService.java (4)

37-38: 이벤트 퍼블리셔와 파일 리포지토리 의존성 주입 추가 확인

ApplicationEventPublisherFileRepository를 의존성으로 추가하여 이벤트 기반 아키텍처로의 전환을 잘 반영하였습니다.


46-46: TalkPick 생성 시 이벤트 발행으로 비동기 처리 개선

createTalkPick 메서드에서 eventPublisher를 통해 TalkPickCreatedEvent를 발행하여 파일 처리와 요약 생성 작업을 트랜잭션 후에 비동기로 처리하도록 리팩토링한 점이 적절합니다.


89-90: TalkPick 업데이트 시 이벤트 발행으로 처리 흐름 개선

updateTalkPick 메서드에서 TalkPickUpdatedEvent를 발행하여 업데이트 관련 비동기 처리를 효율적으로 관리한 점이 좋습니다.


99-99: TalkPick 삭제 시 이벤트 발행으로 비동기 처리 구현

deleteTalkPick 메서드에서 TalkPickDeletedEvent를 발행하여 삭제 관련 후속 작업을 트랜잭션 후에 비동기로 처리하도록 한 점이 적절합니다.

src/main/java/balancetalk/talkpick/domain/event/TalkPickCreatedEvent.java (1)

1-15: TalkPickCreatedEvent 클래스 정의 확인

TalkPickCreatedEvent 클래스가 생성되어 TalkPick 생성 시 필요한 이벤트 정보를 잘 캡슐화하고 있습니다. Lombok 어노테이션을 활용하여 보일러플레이트 코드를 줄인 점이 좋습니다.

src/main/java/balancetalk/talkpick/domain/event/TalkPickUpdatedEvent.java (1)

1-16: TalkPickUpdatedEvent 클래스 정의 확인

TalkPickUpdatedEvent 클래스가 추가되어 TalkPick 업데이트 시 새로운 파일과 삭제할 파일 정보를 효과적으로 전달할 수 있게 되었습니다. 이벤트 기반 아키텍처로의 전환을 잘 반영하였습니다.

src/main/java/balancetalk/talkpick/domain/event/TalkPickEventHandler.java (2)

9-15: 이벤트 핸들러의 의존성 주입 및 구조가 잘 설계되었습니다.

클래스가 단일 책임 원칙을 잘 따르고 있으며, 필요한 서비스들이 적절히 주입되어 있습니다.


29-32: 삭제 이벤트 처리가 적절합니다.

파일 삭제 로직이 명확하고 간단하게 구현되어 있습니다.

src/main/java/balancetalk/talkpick/application/TalkPickFileService.java (1)

27-29: null 체크 로직이 적절합니다.

파일 ID 리스트에 대한 null과 empty 체크를 통해 불필요한 처리를 방지하고 있습니다.

src/main/java/balancetalk/talkpick/application/TalkPickSummaryService.java (1)

58-59: @Async@Transactional 사용 시 트랜잭션 전파 확인 필요

@Async를 사용하면 메서드가 별도의 스레드에서 실행되므로, @Transactional이 의도한 대로 작동하지 않을 수 있습니다. 트랜잭션 전파에 문제가 없는지 확인하고, 필요한 경우 @Transactional 애너테이션을 summarize 메서드로 이동하는 것을 고려해 보세요.

Comment on lines 74 to 75
log.error("Fail to summary TalkPick ID = {}", talkPickId);
log.error("exception message = {} {}", e.getMessage(), e.getStackTrace());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

로그 메시지 및 예외 처리 개선 필요

로그 메시지의 문법 오류를 수정하고, 예외와 스택 트레이스를 올바르게 로깅하기 위해 log.error 메서드의 사용을 개선해야 합니다.

다음과 같이 수정하는 것을 제안합니다:

-log.error("Fail to summary TalkPick ID = {}", talkPickId);
-log.error("exception message = {} {}", e.getMessage(), e.getStackTrace());
+log.error("Failed to summarize TalkPick ID = {}", talkPickId, e);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.error("Fail to summary TalkPick ID = {}", talkPickId);
log.error("exception message = {} {}", e.getMessage(), e.getStackTrace());
log.error("Failed to summarize TalkPick ID = {}", talkPickId, e);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (1)

71-72: 테스트 메서드 변경이 일관성 있게 이루어졌습니다!

파일 저장소 메서드 호출로의 변경이 모든 테스트 케이스에 올바르게 적용되었습니다.

테스트 유지보수성 개선을 위한 제안

여러 테스트 메서드에서 동일한 mock 설정이 반복되고 있습니다. 이를 @BeforeEach에서 처리하는 것이 좋을 것 같습니다.

다음과 같이 수정하는 것을 고려해보세요:

 @BeforeEach
 void setUp() {
     talkPick = mock(TalkPick.class);
     member = mock(Member.class);
     guestOrApiMember = mock(GuestOrApiMember.class);

     when(talkPick.getId()).thenReturn(1L);
     // ... existing setup ...
+    when(fileRepository.findImgUrlsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of());
+    when(fileRepository.findIdsByResourceIdAndFileType(1L, TALK_PICK)).thenReturn(List.of());
 }

Also applies to: 87-88, 103-104

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d650287 and 938b963.

📒 Files selected for processing (1)
  • src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (5 hunks)
🔇 Additional comments (2)
src/test/java/balancetalk/talkpick/application/TalkPickServiceTest.java (2)

3-3: 적절한 임포트 변경이 이루어졌습니다!

파일 처리 로직의 아키텍처 변경에 따른 임포트 수정이 올바르게 반영되었습니다.

Also applies to: 11-11


39-39: Mock 객체 변경이 적절합니다!

TalkPickFileHandler에서 FileRepository로의 의존성 변경이 테스트에 올바르게 반영되었습니다.

@JECT-Study JECT-Study deleted a comment from notion-workspace bot Jan 20, 2025
@Hanjaemo Hanjaemo merged commit ed0f965 into main Jan 20, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
D-0 긴급 이슈에 대한 작업 🛠️ fix 오류 수정 🪄 refactor 리팩토링
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

톡픽 생성 API 요청 시 일정 확률로 NOT_FOUND_TALK_PICK 예외 발생
3 participants